package solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import views.MachineReassignmentMay2012;

import command.Invoker;

import model.EnhancedCostEvaluator;
import model.IncrementalCostCalculator;
import model.IncrementalCostCalculatorNoBalance;
import model.IncrementalValidator;
import model.Neighborhood;
import model.Problem;
import model.Process;
import model.Service;
import model.Solution;
import model.SolverEnvironment;

public abstract class BaseSolver {
	protected static Logger log = LoggerFactory.getLogger(BaseSolver.class);

	protected String saved_solution_filename = "default_solution_filename.txt";

	public void setSaved_solution_filename(String saved_solution_filename) {
		this.saved_solution_filename = saved_solution_filename;
	}

	SolverEnvironment env;
	private SelectionAgent selectionAgent;

	protected long best_cost = Long.MAX_VALUE;

	protected long current_cost = Long.MAX_VALUE;

	protected long potential_cost = Long.MAX_VALUE;

	protected Solution best_solution;

	protected int noImprove = 0;

	protected int noBestImprove = 0;

	protected long iterations = 0;

	public BaseSolver(SolverEnvironment env) {
		this.env = env;
		best_cost = current_cost = env.getCostCalculator().getNewSolutionCost();	
		getCurrentSolution().setCost(current_cost);
		getCurrentSolution().setCostComponents(env.getCostCalculator().getCostComponentMemory());
		best_solution = getCurrentSolution().copy();
		this.selectionAgent = new SelectionAgent(env);
		env.problem.computeTransientResourcesRemainingCapacityPerMachine(env);
//		env.problem.computeMachinesEligibleProcessesLists(env);
	}

	@Deprecated
	public BaseSolver(Problem problem, Solution initial_solution,
			Solution new_solution) {
		IncrementalCostCalculator icc;
		if (problem.getBalanceObjectives().size() > 0)
			icc = new IncrementalCostCalculator();
		else
			icc = new IncrementalCostCalculatorNoBalance();

		env = new SolverEnvironment(problem, initial_solution, new_solution,
				new Random(Problem.RANDOM_SEED), new Invoker(),
				new IncrementalValidator(), icc, new EnhancedCostEvaluator());

		best_cost = current_cost = env.getCostCalculator().getNewSolutionCost();
		env.getCurrentSolution().setCost(current_cost);
		env.getCurrentSolution().setCostComponents(
				env.getCostCalculator().getCostComponentMemory());
		best_solution = getCurrentSolution().copy();

		this.selectionAgent = new SelectionAgent(env);
		env.problem.computeTransientResourcesRemainingCapacityPerMachine(env);
		// env.problem.computeMachinesEligibleProcessesLists(env);
	}

	public BaseSolver(Problem problem, Solution initial_solution) {
		this(problem, initial_solution, initial_solution.copy());
	}

	public int[][] generateRandomMoves(int numberOfProcesses) {
		int P_SIZE = numberOfProcesses;
		List<Integer> selectedProcesses = new ArrayList<Integer>();
		if (getProblem().P < P_SIZE) {
			P_SIZE = getProblem().P;
		}
		while (selectedProcesses.size() < P_SIZE) {
			int x = getRandom().nextInt(getProblem().P);
			if (!selectedProcesses.contains(x))
				selectedProcesses.add(x);
		}
		int[][] moves = new int[P_SIZE][2];
		int i = 0;
		for (Integer p_index : selectedProcesses) {
			int m_index = getRandom().nextInt(getProblem().M);
			moves[i][0] = p_index;
			moves[i][1] = m_index;
			// System.out.printf("Move process %d to machine %d\n", p_index,
			// m_index);
			i++;
		}
		return moves;
	}

	public Solution getCurrentSolution() {
		return env.getCurrentSolution();
	}

	public void saveSolution(int problemno) {
		EnhancedCostEvaluator ece = new EnhancedCostEvaluator();
		ece.setSolutionPair(getInitialSolution(), getCurrentSolution());
		ece.computeCost();
		boolean f = ece.isFeasible();
		String fn = String.format("assignment_a%d_sol_%s%s.txt", problemno,
				ece.getNewSolutionCost(), f ? "" : "_inf");
		getCurrentSolution().exportToFileName(fn);
		log.debug("File " + fn + " saved!");
	}

	public void saveSolution(long cost, int time_elapsed, boolean costOnFilename) {
		if (!costOnFilename) {
			getCurrentSolution().exportToFileName(saved_solution_filename);
		} else {
			String fn = saved_solution_filename.replace(".txt",
					String.format("_t%03d_c%,d.txt", time_elapsed, cost));
			getCurrentSolution().exportToFileName(fn);
		}
		// System.out.printf("Solution saved %03dsecs %,d\n", time_elapsed,
		// cost);
	}

	public void saveSolution(String problemno, String label) {
		EnhancedCostEvaluator ece = env.getFullValidator();
		// ece.setSolutionPair(getInitialSolution(), getCurrentSolution());
		ece.computeCost();
		boolean f = ece.isFeasible();
		String fn = String.format("assignment_%s_%s_%s%s.txt", problemno,
				label, ece.getNewSolutionCost(), f ? "" : "_inf");
		getCurrentSolution().exportToFileName(fn);
		log.debug("File " + fn + " saved!");
	}

	protected int getNumberOfMovedProcesses(Solution aSolution) {
		int c = 0;
		for (int p = 0; p < getProblem().P; p++) {
			if (getInitialSolution().getMachineIdForProcessId(p).intValue() != aSolution
					.getMachineIdForProcessId(p).intValue())
				c++;
		}
		return c;
	}

	public void printEligibleMachinesPerProcess(List<Integer> selectedProcesses) {
		for (Integer p_id : selectedProcesses) {
			Process process = getProblem().getProcesses().get(p_id);
			// System.out.println(process.getEligibleMachinesInfo());
			StringBuilder sb = new StringBuilder();
			for (Integer m_id : process.getEligibleMachines()) {
				sb.append(String.format("m%d_n%d_L%d, ", m_id, getProblem()
						.getMachines().get(m_id).getNeighborhood(),
						getProblem().getMachines().get(m_id).getLocation()));
			}
			if (sb.length() != 0) {
				sb.deleteCharAt(sb.length() - 1);
				sb.deleteCharAt(sb.length() - 1);
			}
			System.out.printf("[p%d_s%d] machines=[%s]\n", p_id,
					process.getService(), sb.toString());
		}
	}

	protected void filterMachinesSatisfyingConflictsConstraint(
			List<Integer> selectedProcesses) {
		for (Integer p_id : selectedProcesses) {
			Process proc = getProblem().getProcesses().get(p_id);
			Service service = getProblem().getServices().get(proc.getService());
			for (Integer p : service.getProcesses()) {
				if (p.intValue() != p_id.intValue()) {
					int m_id = getCurrentSolution().getMachineIdForProcessId(p);
					proc.removeElligibleMachine(m_id);
				}
			}
		}
	}

	// one process for each service in enforced by calling
	// filterMachinesSatisfyingConflictsConstraint
	protected void filterMachinesSatisfyingSpreadConstraints(
			List<Integer> selectedProcesses) {
		for (Integer p_id : selectedProcesses) {
			int s_id = getProblem().getServiceIdForProcessId(p_id);
			Service aService = getProblem().getServices().get(s_id);
			int c_m_id = getCurrentSolution().getMachineIdForProcessId(p_id);
			int c_l_id = getProblem().getLocationIdForMachineId(c_m_id);
			int spread = getCurrentSolution().getSpreadForService(s_id);
			if (spread > aService.getSpreadMin())
				continue;
			if (getCurrentSolution()
					.anotherProcessOfServiceExistsInSameLocation(p_id))
				continue;
			if (spread == aService.getSpreadMin()) {
				List<Integer> m_ids_to_be_removed = new ArrayList<Integer>();
				for (int l_id = 0; l_id < getProblem().L; l_id++) {
					// System.out.printf("L%d machines: %s\n", l_id,
					// problem.getMachinesIdsForLocationId(l_id));
					if (l_id == c_l_id)
						continue;
					if (getCurrentSolution().hasProcessFromServiceInLocation(
							s_id, l_id)) {
						m_ids_to_be_removed.addAll(getProblem()
								.getMachinesIdsForLocationId(l_id));
					}
				}
				Process aProcess = getProblem().getProcesses().get(p_id);
				aProcess.removeElligibleMachines(m_ids_to_be_removed);
			}
		}
	}

	// produces poor results for model_a1_4.txt
	protected void filterMachinesSatisfyingDependencyConstraints(
			List<Integer> selectedProcesses) {
		for (Integer p_id : selectedProcesses) {
			int s_id = getProblem().getServiceIdForProcessId(p_id);
			if (getProblem().isServiceDependedEitherWay(s_id)) {
				int c_m_id = getCurrentSolution()
						.getMachineIdForProcessId(p_id);
				int c_n_id = getProblem().getNeighborhoodIdForMachineId(c_m_id);
				List<Integer> machines_to_be_removed = new ArrayList<Integer>();
				Process aProcess = getProblem().getProcesses().get(p_id);
				for (Integer m_id : aProcess.getEligibleMachines()) {
					int n_id = getProblem().getNeighborhoodIdForMachineId(m_id);
					if (n_id != c_n_id)
						machines_to_be_removed.add(m_id);
				}
				for (Integer m_id : machines_to_be_removed) {
					aProcess.removeElligibleMachine(m_id);
				}
			}
		}
	}

	public void setCurrentSolution(Solution aSolution) {
		env.setCurrentSolution(aSolution);
	}

	/**
	 * 
	 * @param nop
	 *            number of processes to be selected
	 * @param noem
	 *            number of machines to be selected for each process
	 * 
	 * @return a List of selected processes with each one having an associated
	 *         list of machines to be moved to. The list of machines for each
	 *         process includes the machine that the process is in the
	 *         current_solution. Processes and machines are selected randomly.
	 */
	public List<Integer> selectFullRandomProcessesAndMachines(int nop, int noem) {
		nop = nop < getProblem().P ? nop : getProblem().P;
		noem = noem < getProblem().M ? noem : getProblem().M;
		List<Integer> selectedProcesses = new ArrayList<Integer>();

		List<Integer> ballot_processes = new ArrayList<Integer>();
		for (int p = 0; p < getProblem().P; p++) {
			ballot_processes.add(p);
		}
		Collections.shuffle(ballot_processes, getRandom());
		for (int i = 0; i < nop; i++) {
			selectedProcesses.add(ballot_processes.get(i));
		}
		List<Integer> ballot_machines = new ArrayList<Integer>();
		for (int m = 0; m < getProblem().M; m++) {
			ballot_machines.add(m);
		}
		selectionAgent.setSolution(getCurrentSolution());
		selectionAgent.randomlySelectMachinesForProcesses(selectedProcesses,
				ballot_machines, noem);
		clearConflictConstraints(selectedProcesses);
		// selectionAgent.printTaboo();
		return selectedProcesses;
	}

	List<Integer> selectMachinesFromTheSameNeighborhood(int nop, int noem) {
		List<Integer> selectedProcesses = new ArrayList<Integer>();
		int selectedNeighborhood = getRandom().nextInt(getProblem().N);
		List<Integer> ballot_machines = getProblem()
				.getMachinesIdsForNeighborhoodId(selectedNeighborhood);
		List<Integer> eligibleProcesses = new ArrayList<Integer>();
		for (Integer m_id : ballot_machines) {
			eligibleProcesses.addAll(getCurrentSolution()
					.getProcessesForMachine(m_id));
		}
		Collections.shuffle(eligibleProcesses, getRandom());
		if (eligibleProcesses.size() < nop) {
			nop = eligibleProcesses.size();
		}
		for (int p = 0; p < nop; p++) {
			selectedProcesses.add(eligibleProcesses.get(p));
		}

		selectionAgent.setSolution(getCurrentSolution());
		selectionAgent.randomlySelectMachinesForProcesses(selectedProcesses,
				ballot_machines, noem);
		clearConflictConstraints(selectedProcesses);
		return selectedProcesses;
	}

	List<Integer> selectMachinesFromTheSameLocation(int nop, int noem) {
		List<Integer> selectedProcesses = new ArrayList<Integer>();
		int selectedLocation = getRandom().nextInt(getProblem().L);
		List<Integer> ballot_machines = getProblem()
				.getMachinesIdsForLocationId(selectedLocation);
		List<Integer> eligibleProcesses = new ArrayList<Integer>();
		for (Integer m_id : ballot_machines) {
			eligibleProcesses.addAll(getCurrentSolution()
					.getProcessesForMachine(m_id));
		}
		Collections.shuffle(eligibleProcesses, getRandom());
		if (eligibleProcesses.size() < nop) {
			nop = eligibleProcesses.size();
		}
		for (int p = 0; p < nop; p++) {
			selectedProcesses.add(eligibleProcesses.get(p));
		}
		selectionAgent.setSolution(getCurrentSolution());
		selectionAgent.randomlySelectMachinesForProcesses(selectedProcesses,
				ballot_machines, noem);
		clearConflictConstraints(selectedProcesses);
		return selectedProcesses;
	}

	List<Integer> selectMachinesFromMoreAndLessLoadedMachines(int nop, int noem) {
		double processesBelongingToHeavyLoadedMachinesRatio = 0.7;
		double heavyLoadedMachinesPercentage = 0.3;

		int processesBelongingToHeavyLoadedMachines = (int) (noem * processesBelongingToHeavyLoadedMachinesRatio);
		int processesBelongingToLightLoadedMachines = nop
				- processesBelongingToHeavyLoadedMachines;
		int heavyMachinesSize = (int) (getProblem().M * heavyLoadedMachinesPercentage);
		selectionAgent.setSolution(getCurrentSolution());
		List<Integer> selectedProcesses = selectionAgent.selectProcesses(
				heavyMachinesSize, processesBelongingToHeavyLoadedMachines,
				processesBelongingToLightLoadedMachines);

		List<Integer> ballot_machines = new ArrayList<Integer>();
		for (int m = 0; m < getProblem().M; m++) {
			ballot_machines.add(m);
		}
		selectionAgent.randomlySelectMachinesForProcesses(selectedProcesses,
				ballot_machines, noem);
		clearConflictConstraints(selectedProcesses);
		return selectedProcesses;
	}

	// Selected processes should not have as eligible machines machines that
	// already have another process of the same service that could not be moved.
	@Deprecated
	protected void clearConflictConstraints(List<Integer> selectedProcesses) {
		List<Integer> processesWithOnlyOneEligibleMachine = new ArrayList<Integer>();
		for (Integer p_id : selectedProcesses) {
			Process selectedProcess = getProblem().getProcesses().get(p_id);
			int s_id = getProblem().getServiceIdForProcessId(p_id);
			for (Integer p_id2 : getProblem().getProcessesIdsForServiceId(s_id)) {
				if (p_id == p_id2)
					continue;
				if (selectedProcesses.contains(p_id2))
					continue;
				int m_id = getCurrentSolution().getMachineIdForProcessId(p_id2);
				if (selectedProcess.getEligibleMachines().contains(
						new Integer(m_id))) {
					selectedProcess.removeElligibleMachine(m_id);
					// System.out
					// .printf("Process p%d_s%d machine m%d removed because p%d_%d of the same service is scheduled in it\n",
					// p_id, s_id, m_id, p_id2, s_id);
				}
			}
			if (selectedProcess.getEligibleMachines().size() == 1) {
				processesWithOnlyOneEligibleMachine.add(p_id);
			}
			if (selectedProcess.getEligibleMachines().isEmpty()) {
				throw new IllegalStateException(
						"selected process should have at least 1 eligible machine");
			}
		}
		if (!processesWithOnlyOneEligibleMachine.isEmpty()) {
			System.out
					.println("The following processes have only one eligible machine so they are eliminated: "
							+ processesWithOnlyOneEligibleMachine);
			selectedProcesses.removeAll(processesWithOnlyOneEligibleMachine);
		}
	}

	protected long computeCutOff(List<Integer> selectedProcesses) {
		return getFullValidator().getNewSolutionCost();
		// Set<Integer> involved_machines = new HashSet<Integer>();
		// for (Integer p : selectedProcesses) {
		// Process aProcess = problem.getProcesses().get(p);
		// involved_machines.addAll(aProcess.getEligibleMachines());
		// }
		// // load cost
		// long sum = 0;
		// for (int r = 0; r < problem.RE; r++) {
		// long sum2 = 0;
		// for (Integer m : involved_machines) {
		// int usage = current_solution.getUsageOfMachineForResource(m, r);
		// int safety_capacity = problem.SC[m][r];
		// for (Integer p : current_solution.getProcessesForMachine(m)) {
		// if (selectedProcesses.contains(p)) {
		// safety_capacity -= problem.R[p][r];
		// }
		// }
		// int delta = usage - safety_capacity;
		// int max;
		// if (delta > 0)
		// max = delta;
		// else
		// max = 0;
		// System.out.printf("m%d_r%d lc=%,d\n", m, r, max);
		// sum2 += max * problem.getResourceByIndex(r).getWeightLoadCost();
		// }
		// sum += sum2;
		// }
		// System.out.printf("LC=%,d BC=%,d PMC=%,d SMC=%,d MMC=%,d\n", sum, 0,
		// ece.getProcessMoveCost(), ece.getServiceMoveCost(),
		// ece.getMachineMoveCost());
		// return sum + ece.getProcessMoveCost() + ece.getServiceMoveCost()
		// + ece.getMachineMoveCost();
	}

	// a simple bubble sort for starters
	protected void sort(long[] cc, int[] m_a) {
		for (int i = 1; i < cc.length; i++) {
			for (int j = cc.length - 1; j >= i; j--) {
				if (cc[j - 1] < cc[j]) {
					long temp1 = cc[j];
					cc[j] = cc[j - 1];
					cc[j - 1] = temp1;
					int temp2 = m_a[j];
					m_a[j] = m_a[j - 1];
					m_a[j - 1] = temp2;
				}
			}
		}
	}

	public abstract int solveWithTimeLimit(int seconds) throws Exception;

	protected Problem getProblem() {
		return env.getProblem();
	}

	protected Solution getInitialSolution() {
		return env.getInitialSolution();
	}

	protected Random getRandom() {
		return env.getRandom();
	}

	protected EnhancedCostEvaluator getFullValidator() {
		return env.getFullValidator();
	}

	protected Solution getPotentialSolution() {
		return env.getPotentialSolution();
	}

	protected void setPotentialSolution(Solution potential_solution) {
		env.setPotentialSolution(potential_solution);
	}

	protected boolean isIncomplete(Solution sol) {
		for (int p_id = 0; p_id < getProblem().P; p_id++) {
			if (sol.getMachineIdForProcessId(p_id) == -1) {
				return true;
			}
		}
		return false;
	}

	public long getBestCost() {
		return best_cost;
	}

	public Solution getBestSolution() {
		synchronized (best_solution) {
			return best_solution.lightCopy();			
		}
	}

	public void setBestSolution(Solution newSol) {
		synchronized (best_solution) {
			if(newSol.getCost()<best_cost){
				best_solution = newSol;
				best_cost = best_solution.getCost();
			}
		}
	}

	public boolean saveCurrentToBest() {
			if (potential_cost < best_cost) {
	//			 System.out.printf("%d:Better cost found %d from %d\n",
	//			 iterations, potential_cost,
	//			 best_cost);
	//			 if(1.2*potential_cost < best_cost){
	//				 System.out.println("Magic Move:"+lastMove);
	//				 System.out.printf("%d:Better cost found %d from %d(%f)\n",
	//						 iterations, potential_cost,
	//						 best_cost,((best_cost-potential_cost)/(double)best_cost));
	//			 }
				synchronized (best_solution) {
					best_cost = potential_cost;
					getPotentialSolution().setCost(best_cost);
					getPotentialSolution().setCostComponents(env.getCostCalculator().saveAndGetCostComponentMemory());
					best_solution = getPotentialSolution().lightCopy();
				}		
				
				noBestImprove=0;
				noImprove = 0;
				
				return true;
			}
			return false;
		}

	public SolverEnvironment getEnv() {
		return env;
	}
}
